package com.freetymekiyan.algorithms.level.hard;

import java.util.Arrays;

/**
 * 188. Best Time to Buy and Sell Stock IV
 * <p>
 * Say you have an array for which the ith element is the price of a given
 * stock on day i.
 * <p>
 * Design an algorithm to find the maximum profit. You may complete at most k
 * transactions.
 * <p>
 * Note:
 * You may not engage in multiple transactions at the same time (ie, you must
 * sell the stock before you buy again).
 * <p>
 * Tags: DP
 */
class BestTimeToBuyAndSellStock4 {

  public static void main(String[] args) {
    // 2, [3,2,6,5,0,3]
    // 2, [3,3,5,0,0,3,1,4]
    BestTimeToBuyAndSellStock4 b = new BestTimeToBuyAndSellStock4();
    int[] A = new int[]{3, 3, 5, 0, 0, 3, 1, 4};
    int[] B = new int[]{3, 2, 6, 5, 0, 3};
    System.out.println(b.maxProfitOpt(2, A));
    System.out.println(b.maxProfit(2, A));
    System.out.println(b.maxProfitOpt(2, B));
    System.out.println(b.maxProfit(2, B));
  }

  /**
   * DP, bottom-up, O(kn) Time, O(n) Space
   * If k >= n/2, we can have transactions any time, O(n).
   * dp[k][i+1] represents the max profit of using [0, i] and k transactions
   * It can be dp[k-1][i+1](add 1 more transaction changes nothing)
   * It can be dp[k][i](prices[i] changes nothing)
   * It can be prices[i] + max(dp[k-1][j] - prices[j]), 0 <= j < i
   * means prices[i] will change the max profit, find the biggest from k-1
   * transacions and add prices[i]
   * dp[k][i+1] = max(dp[k-1][i+1], dp[k][i], prices[i] + max(dp[k-1][j] -
   * prices[j])), (0 <= j < i)
   */
  public int maxProfitOpt(int k, int[] prices) {
    if (prices == null || prices.length < 2 || k == 0) {
      return 0;
    }
    int n = prices.length;
    int res = 0;
    if (k >= n / 2) { // As many transactions as possible
      for (int i = 1; i < n; i++) {
        if (prices[i] > prices[i - 1]) {
          res += prices[i] - prices[i - 1];
        }
      }
      return res;
    }
    int[] cur = new int[n + 1];
    for (int i = 1; i <= k; i++) {
      int curMax = Integer.MIN_VALUE;
      for (int j = 0; j < n; j++) {
        int temp = cur[j + 1];
        cur[j + 1] = Math.max(Math.max(cur[j + 1], cur[j]), prices[j] + curMax);
        System.out.print(curMax + "|");
        curMax = Math.max(curMax, temp - prices[j]);
        System.out.print(curMax + "\n");
      }
      System.out.println(Arrays.toString(cur));
    }
    return cur[n];
  }

  /**
   * DP, bottom-up, O(kn) Time, O(kn) Space
   */
  public int maxProfit(int k, int[] prices) {
    if (prices == null || prices.length < 2 || k == 0) {
      return 0;
    }
    int n = prices.length;
    int res = 0;
    if (k >= n / 2) {
      for (int i = 1; i < n; i++) {
        if (prices[i] > prices[i - 1]) {
          res += prices[i] - prices[i - 1];
        }
      }
      return res;
    }
    int[][] dp = new int[k + 1][n + 1];
    for (int i = 1; i <= k; i++) {
      int curMax = Integer.MIN_VALUE;
      for (int j = 0; j < n; j++) {
        dp[i][j + 1] = Math.max(Math.max(dp[i - 1][j + 1], dp[i][j]), prices[j] + curMax);
        curMax = Math.max(curMax, dp[i - 1][j] - prices[j]);
      }
    }
    return dp[k][n];
  }
}
